﻿using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;
using vBridge.Dwm.Aero.AeroAvailabilityCheckers;
using vBridge.Dwm.Aero.Glassers;
using vBridge.Dwm.Aero.Margins;
using vBridge.Properties;
using vBridge.Win32;

namespace vBridge.Wpf.Aero
{
    // Note for developer: this type's purpose is to combine loose types.

    /// <summary>
    ///     An aero-glassed window.
    /// </summary>
    public class GlassWindow : Window, IWin32Window, INotifyPropertyChanged, IDisposable
    {
        #region Members

        private MarginsViewModel _aeroMargins;
        private IWindowAeroGlasser _glasser;
        private IntPtr _windowHandle;
        IMessageHook _wpfMessageHook;

        /// <summary>
        /// Background brush that will be used when aero is not available.
        /// </summary>
        public Brush OriginalBackground { get; set; }

        /// <summary>
        ///     The Margin for the client-area.
        /// </summary>
        public MarginsViewModel AeroMargins
        {
            get { return _aeroMargins; }
            set
            {
                if (Equals(value, _aeroMargins)) return;
                _aeroMargins = value;
                OnPropertyChanged();
            }
        }

        #endregion

        #region Constructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="GlassWindow" /> class.
        /// </summary>
        /// <remarks>
        ///     Constructs negative Margin (that is, glasses the entire window).
        /// </remarks>
        public GlassWindow()
            : this(new Margin(-1))
        {
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="GlassWindow" /> class.
        /// </summary>
        /// <param name="margins">The Margin that will be used to glass the window.</param>
        public GlassWindow(IMargins margins)
        {
            AeroMargins = new MarginsViewModel(margins);
            Loaded += OnLoaded;
            PropertyChanged += (sender, args) =>
                               {
                                   if (args.PropertyName == "AeroMargins")
                                       OnAeroMarginsChanged();
                               };
        }

        protected override void OnSourceInitialized(EventArgs e)
        {
            _wpfMessageHook = new WpfMessageHook(this);
            _wpfMessageHook.SetProcedure(GlassProcedure);
        }

        #endregion

        #region INotifyPropertyChanged + INotifyPropertyChanging

        public event PropertyChangedEventHandler PropertyChanged;
        public event PropertyChangingEventHandler PropertyChanging;

        [NotifyPropertyChangedInvocator]
        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }

        protected virtual void OnPropertyChanging([CallerMemberName] string propertyName = null)
        {
            PropertyChangingEventHandler handler = PropertyChanging;
            if (handler != null) handler(this, new PropertyChangingEventArgs(propertyName));
        }

        #endregion

        #region IWin32Window

        public IntPtr Handle
        {
            get { return _windowHandle; }
        }

        #endregion


        private void OnAeroMarginsChanged()
        {
            AeroMargins.PropertyChanged -= AeroMarginsOnPropertyChanged;
            _glasser.GlassWindow(AeroMargins);
            AeroMargins.PropertyChanged += AeroMarginsOnPropertyChanged;
        }


        private void OnLoaded(Object sender, RoutedEventArgs routedEventArgs)
        {
            _windowHandle = new WindowInteropHelper(this).Handle;

            var unsafeGlasser = new WpfWindowAeroGlasser(new WindowAeroGlasser(_windowHandle), this);
            _glasser = new SafeAeroGlass(unsafeGlasser,
                new SystemVersionAeroAvailabilityChecker(),
                new CompositionAeroAvailabilityChecker());
            MakeWindowGlassed();
            AeroMargins.PropertyChanged += AeroMarginsOnPropertyChanged;
        }

        private IntPtr GlassProcedure(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, ref Boolean handled)
        {
            if (msg != 0x031E) return IntPtr.Zero;

            Background = OriginalBackground;
            MakeWindowGlassed();
            return IntPtr.Zero;
        }

        private void AeroMarginsOnPropertyChanged(Object sender, PropertyChangedEventArgs propertyChangedEventArgs)
        {
            MakeWindowGlassed();
        }

        private void MakeWindowGlassed()
        {
            _glasser.GlassWindow(AeroMargins);
        }


        #region IDisposable

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(Boolean disposing)
        {
            if (!disposing) return;

            // The default wpf message hook is, and others are likely to be disposable.
            var disposableWpfMessageHook = _wpfMessageHook as IDisposable;
            if (disposableWpfMessageHook != null)
                disposableWpfMessageHook.Dispose();
        }

        #endregion

    }
}